package com.chunuo.configuration.starter;

import com.chunuo.configuration.Application;
import com.chunuo.configuration.annotation.*;
import com.chunuo.configuration.constant.AnnotationConstant;
import com.chunuo.configuration.exception.ChunuoException;
import com.chunuo.configuration.interfaces.ComponentScan;
import com.chunuo.configuration.proxy.AOPProxy;
import com.chunuo.configuration.proxy.DaoProxy;
import com.chunuo.configuration.utils.AnnotationUtils;
import com.mysql.cj.util.StringUtils;

import javax.naming.Name;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.sql.Time;
import java.util.*;

/**
 * 扫描注解、注入对象
 *
 * @author liujiamiao
 * @email 2544296058@qq.com
 * @date 2020-12-22 12:55
 */
public class ComponentAutoConfiguration {


    //对象map
    //      key： 类名
    //      val： 类的实例化对象
    private Map<String,Object> objectMap = new HashMap<>();

    //属性map
    //      key：全限定类名+属性名
    //      val：类名
    private Map<String,String> fieldMap = new HashMap<>();

    //属性map
    //      key：uri,比如：user/login
    //      val：objectMap中的key_方法名
    private Map<String,String> requestMap = new HashMap<>();

    /**
     * 创建bean时，如果有bean是需要参数的，那么只有将参数bean创建完再创建它
     */
    private Map<Method,Object> beanMap = new HashMap();


    public ComponentAutoConfiguration(File path, List<String> scan) {
        //将注解的类和属性都存进map中
        parsePath(path, scan,new ComponentScan() {
            /**
             *
             * @param scan 扫描集合，比如dao报下需要动态代理、service需要切面编程写事务
             * @param clazz 目前是哪个类
             * @param allClassName 全限定类名
             */
            @Override
            public void annotationScan(List<String> scan,  Class clazz, String allClassName) {
                if(scan.size()>=AnnotationConstant.DAO&&allClassName.indexOf(scan.get(AnnotationConstant.DAO))>=0){
                    //dao层自己注入，因为需要动态代理创建实现接口对象

                    Object obj = new DaoProxy().getDaoProxy(clazz);
                    //首字母小写
                    String className = AnnotationUtils.getClassName(clazz);
                    //获取注解value
                    Annotation annotation = clazz.getAnnotation(Component.class);
                    String annotationName = null;
                    if(annotation!=null){
                        annotationName = AnnotationUtils.getAnnotationName(annotation);
                    }
                    //添加bean
                    String[] names = ioc(clazz,new String[]{allClassName,className,annotationName });
                    addBean(names,obj);
                }else{
                    autoComponentScan(clazz);
                }
            }
        });

        int provSize = beanMap.size();
        int size = -1;
        while(beanMap.size()!=0){
            if(provSize==size){
                String message = "创建bean异常,其参数对象不存在，或者两个对象互相依赖";
                System.out.println(new Date()+"  发送错误，错误警告 = 》 "+message);
                throw new ChunuoException(message);
            }
            List<Method> array = new ArrayList<>();
            //开始还没创建完的bean
            for(Map.Entry<Method,Object> entry : beanMap.entrySet()){
                if(addBean(entry.getKey(), entry.getValue(),1)){
                    array.add(entry.getKey());
                }
            }
            //删去那些已经创建成功的
            for(Method key : array){
                beanMap.remove(key);
            }
            array.removeAll(array);
            size = beanMap.size();
        }


        //注入绑定数据
        loadValue();

        //开启事务动态代理创建对象后，因为无法再反射到属性和方法，
        // 所以只能在所有扫描完成并注入数据后再开始扫描事务类
        parsePath(path, scan, new ComponentScan() {
            /**
             *
             * @param scan 扫描集合，比如dao报下需要动态代理、service需要切面编程写事务
             * @param clazz 目前是哪个类
             * @param allClassName 全限定类名
             */
            @Override
            public void annotationScan(List<String> scan,  Class clazz, String allClassName) {
                if (scan.size() >= AnnotationConstant.SERVICE &&allClassName.indexOf(scan.get(AnnotationConstant.SERVICE)) >= 0) {
                    //是否有这个注解
                    if(clazz.isAnnotationPresent(Component.class)){
                        //拿到component注解
                        Annotation annotation = clazz.getAnnotation(Component.class);
                        //注解使用的名字，可能没写
                        String annotationName = AnnotationUtils.getAnnotationName(annotation);
                        String className = AnnotationUtils.getClassName(clazz);

                        //从map中取出之前存的，再动态代理创建对象
                        String[] names = ioc(clazz,new String[]{allClassName,className,annotationName });
                        for(String name:names){
                            if(name!=null){
                                Object obj = objectMap.get(name);
                                if(obj!=null){
                                    objectMap.put(name,new AOPProxy().transactionProxy(obj));
                                }
                            }
                        }

                    }else{
                        //没加上注解，所以不管
                    }

                }
            }
        });

        //注入绑定数据
        loadValue();

    }

    /**
     * 扫描common和autowired注解
     * @param clazz
     */
    private void autoComponentScan( Class clazz) {

        if(clazz!=null&&(clazz.isAnnotationPresent(Component.class))){
            //拿到这个类的COMPONENT注解
            Annotation annotation = clazz.getAnnotation(Component.class);
            //拿到注解的名字@Component(value="名字")
            String annotationName = AnnotationUtils.getAnnotationName(annotation);
            //com.chunuo.autoconfiguration.test.dao.TestDao
            String allClassName = clazz.toString().split(" ")[1];
            //类名
            String className = AnnotationUtils.getClassName(clazz);
            String[] names = null;
            try {
                names = ioc(clazz, new String[]{allClassName, className, annotationName});
                addBean(names,clazz.newInstance());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            //如果注解没写value，则是类名首字母小写
            String key = (annotationName!=null&&!annotationName.isEmpty())?annotationName:className;
            //字段的注入，@Autowired注解
            fieldAnnotation(clazz,key);


            //处理controller
            if(clazz.isAnnotationPresent(RequestMapping.class)){
                RequestMapping RequestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class);

                //拿到controller的地址
                String beginUri = RequestMapping.value();
                //uri地址
                String uri = null;
                Method[] methods = clazz.getDeclaredMethods();
                for(Method method:methods){
                    RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
                    if(methodRequestMapping==null){
                        continue;
                    }
                    String afterUri = methodRequestMapping.value();
                    if(beginUri.charAt(0)!='/'){
                        beginUri = "/"+beginUri;
                    }
                    if(afterUri.charAt(0)!='/'){
                        afterUri = "/"+afterUri;
                    }
                    uri = beginUri + afterUri;
                    System.out.println("Controller层的 uri接口 -> "+uri);

                    StringBuffer args = new StringBuffer();
                    Parameter[] parameters = method.getParameters();//参数
                    Annotation[][] parameterAnnotations = method.getParameterAnnotations();//参数的注解，是个集合
                    for(int i = 0;i<parameters.length;i++){
                        // ;区分参数，第几个参数
                        //Arrays.toString(parameterAnnotations[i]) =》 java.lang.String-[@com.chunuo.configuration.annotation.RequestBody(), @com.chunuo.configuration.annotation.RequestParam(value=user)];
                        args.append(parameters[i].toString().split(" ")[0]+"-"+Arrays.toString(parameterAnnotations[i])+";");
                    }
                    System.out.println(method.getName()+"方法的参数类型以及注解 -> "+args);

                    requestMap.put(uri,key+"_"+method.getName()+"_"+methodRequestMapping.method()+"_"+args);
                }

            }

        }else if(clazz!=null&&clazz.isAnnotationPresent(ControllerAdvice.class)){
            try {
                objectMap.put(AnnotationConstant.CONTROLLERADVICE,clazz.newInstance());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        //扫描配置类 Configuration
        if(clazz!=null&&clazz.isAnnotationPresent(Configuration.class)){
            //com.chunuo.autoconfiguration.test.dao.TestDao
            String allClassName = clazz.toString().split(" ")[1];

            Object o = objectMap.get(allClassName);
            if(o==null){
                //得创建对象
                try {
                    o = clazz.newInstance();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }

            Method[] methods = clazz.getMethods();
            for(Method method : methods){
                Bean annotation = method.getAnnotation(Bean.class);
                if(annotation!=null){
                    //创建bean
                    addBean(method,o,0);
                }
            }

        }

    }

    /**
     * 配置类创建bean，如果bean有参数还未创建，则暂不创建，等参数创建完了再创建
     * @param method
     * @param o
     * @param type 0->第一次创建，如果没有参数，则添加导beanMap中  1则不添加
     */
    private boolean addBean(Method method,Object o,int type){
        //方法名，即是key
        String name = method.getName();
        //bean注解上的value，即优先级最高
        String value = method.getAnnotation(Bean.class).value();
        //如果有value
        if(!value.isEmpty()){
            name = value;
        }
        Parameter[] parameters = method.getParameters();
        List args = new ArrayList();
        for(Parameter parameter : parameters){
            System.out.println(method.getName()+"方法的参数名 -> "+parameter.getName());
            Object arg = objectMap.get(parameter.getName());
            //如果参数还没创建，只能先创建完参数对象，再创建它
            if(arg==null){
                if(type==0){
                    beanMap.put(method,o);
                }
                return false;
//                            throw new ChunuoException(method.getName()+" 方法没有找到 "+parameter.getName()+" 参数。");
            }
            args.add(arg);
        }
        try {
            //调用方法，得到返回值
            Object result = method.invoke(o, args.toArray());
            if(result!=null){
                objectMap.put(name,result);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 扫描类中的所有属性，是否有注解需要注入数据
     * @param clazz
     */
    private void fieldAnnotation(Class clazz, String key) {
        //得到所有的属性
        Field[] fields = clazz.getDeclaredFields();
        //遍历属性
        for(Field field : fields){
            //得到属性注解
            Annotation annotationField = field.getAnnotation(Autowired.class);
            //没有注解的话，跳过，执行下一个字段
            if(annotationField==null){
                continue;
            }
            //获取注解的value @autowired(value="名字");
            String annotationName = AnnotationUtils.getAnnotationName(annotationField);
            //全限定类名
            String allClassName = field.toString().split(" ")[1];//com.chunuo.autoconfiguration.test.TestClass
            //注解自定义名_属性名_属性全限定类名
            String keyField = annotationName+"_"+field.getName()+"_"+allClassName;
            //存入属性的map中，keyFIeld ：自定义名_属性名_属性全限定类名 ， key：类名

            fieldMap.put(keyField,key);

        }
    }

    /**
     *
     * @param path 扫描包下文件/文件夹
     * @param scan 添加的几个需求，第一个是项目包名、第二个是dao包名、第三个是service.impl包名
     *                  [com\chunuo\configuration\demo, com.chunuo.configuration.demo.dao, com.chunuo.configuration.demo.service.impl]
     * @param componentScan 回调方法
     */
    private void parsePath(File path, List<String> scan, ComponentScan componentScan) {
        if(path.isDirectory()){//判断是否为文件夹
            //得到文件集合
            File[] files = path.listFiles();
            //遍历所有的文件（包含子文件）
            for(File file:files){
                parsePath(file,scan,componentScan);
            }
            //E:\off bangong\IDEA Project\configuration\target\test-classes\com\chunuo\configuration\demo
            //E:\off bangong\IDEA Project\configuration\target\classes\com\chunuo\configuration\demo
        }else if(path.getName().endsWith(".class")){//后缀名为.class
            //将 E:/off bangong/IDEA Project/autoconfiguration/target/classes/com\chunuo\autoconfiguration\test\TestClass.class
            // 转为com.chunuo.autoconfiguration.test.TestClass
            String allClassName = path.getPath().substring(path.getPath().lastIndexOf(scan.get(AnnotationConstant.PACKAGE)), path.getPath().length() - ".class".length()).replace(File.separator, ".");

            Class<?> clazz = null;
            try {
                clazz = Class.forName(allClassName);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }

            componentScan.annotationScan(scan,clazz,allClassName);
        }
    }

    /**
     * 注入数据
     */
    public void loadValue() {
        for(Map.Entry<String,String> entry : fieldMap.entrySet()) {
//            System.out.println(entry.getKey() +"  :  "+entry.getValue());
            //通过属性的value获取对象的
            Object obj = objectMap.get(entry.getValue());
            //如果是动态代理对象则跳过
            if (obj.getClass().toString().indexOf("$") != -1) {
                continue;
            }
            String[] fieldKeys = entry.getKey().split("_");
            //通过属性的key获取对象，属性对象（可能没有写注解名，则默认全限定类名）
            Object fieldObj = objectMap.get(fieldKeys[0].isEmpty() ? fieldKeys[1] : fieldKeys[0]);
            //如果没有找到，则根据全限定类名
            if(fieldObj==null){
                fieldObj = objectMap.get(fieldKeys[2]);
            }
            if(fieldObj==null){
                //如果还是没找到，则找接口
                fieldObj = objectMap.get(fieldKeys[3]);
            }
            if(fieldObj==null){
                throw new ChunuoException(obj.getClass()+"的"+fieldKeys[1]+"字段没有找到 bean！");
            }
            //属性值和对象值都不能为null，否则应该跳过本次循环
            if(fieldObj!=null&&obj!=null){
                try {
                    //获取对象的属性
                    Field objField = obj.getClass().getDeclaredField(fieldKeys[1]);
                    //拿到该字段的访问权限
                    boolean accessible = objField.isAccessible();
                    //再把该属性设为可进入的，公开的，便可访问private属性
                    objField.setAccessible(true);
                    //给这个属性设置值
                    // 这个属性所在的类、属性的值
                    objField.set(obj,fieldObj);
                    //最后把访问权限该回去
                    objField.setAccessible(accessible);
                } catch (NoSuchFieldException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 一个类可能存在多个获取方式，如：类名、注解value、全限定类名、接口全限定类名
     * @param clazz
     * @return
     */
    public String[] ioc(Class clazz,String[] args){
        Class[] interfaces = clazz.getInterfaces();
        //添加bean
        String[] names = new String[3+interfaces.length];
        int len = 0;
        for(String arg : args){
            names[len] = args[len++];
        }
        len = 3;
        for(Class interfacer : interfaces){
            names[len++] = interfacer.toString().split(" ")[1];
        }
        return  names;
    }

    /**
     * 添加类到ioc容器上
     * @param names 一个对象可能会注入三个 -- 全限定类名、首字母小写、注解上的名字
     * @param obj
     */
    public void addBean(String[] names,Object obj) {
        for(String name : names){
            if(name!=null&&!name.isEmpty()){
                Object result = objectMap.get(name);
                if(result==null){
                    objectMap.put(name,obj);
                }else{
                    throw new ChunuoException("ioc创建bean异常，该名字已存在"+result);
                }
            }
        }
    }

    public Map<String, Object> getObjectMap() {
        return objectMap;
    }

    public Map<String, String> getFieldMap() {
        return fieldMap;
    }

    public Map<String,String> getRequestMap(){
        return requestMap;
    }

}
